package org.ala.tcp_feign_client.impl;

import java.util.function.Consumer;

import org.ala.tcp_feign_client.ITcpFeignClient;
import org.ala.tcp_feign_client.handler.TcpFeignClientInitializeHandler;
import org.ala.tcp_feign_common.config.AttrbuteKeyNamespace;
import org.ala.tcp_feign_common.exceptions.ActiveException;
import org.ala.tcp_feign_common.exceptions.LoginException;
import org.ala.tcp_feign_common.logs.LoggerFactory;
import org.ala.tcp_feign_common.msg.AbstractByteMessage;
import org.ala.tcp_feign_common.msg.biz_resp.BizRespByteMessage;
import org.ala.tcp_feign_common.msg.login.LoginByteMessage;
import org.slf4j.Logger;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.AttributeKey;


/**
 * tcp客户端
 * 
 * @author 骆毅(ala)
 * @date 2024年4月19日
 */
public class DefaultTcpFeignClient implements ITcpFeignClient {

	
	static Logger log = LoggerFactory.getLogger("tcp-feign", "client");
	
	
	/**
	 * worker线程组
	 * <p>	默认核数 * 2
	 */
	protected EventLoopGroup workerGroup;
	/**
	 * 远程主机信息host
	 */
	protected String host;
	/**
	 * 远程主机port
	 */
	protected int port;
	/**
	 * 客户端标识后缀
	 * <p>	允许同一个host+port建立多个链接
	 */
	protected String keySuffix;
	/**
	 * 登录用户名
	 */
	protected String username;
	/**
	 * 登录密码
	 */
	protected String password;
	/**
	 * 登录回调
	 */
	protected Consumer<ITcpFeignClient> loginCallback;
	/**
	 * resp回调
	 */
	protected Consumer<BizRespByteMessage> respCallback;
	
	
	public DefaultTcpFeignClient(String host, int port, String keySuffix, 
								 String username, String password) {
		super();
		this.host = host;
		this.port = port;
		this.keySuffix = keySuffix;
		
		this.username = username;
		this.password = password;
	}


	/**
	 * 
	 */
	protected Bootstrap bootstrap;
	/**
	 * 
	 */
	protected Channel mainChannel;
	
	
	/**
	 * 初始化
	 */
	protected void init() {
		workerGroup = new NioEventLoopGroup();
		
		bootstrap = new Bootstrap();
		bootstrap.group(workerGroup)
				  .channel(NioSocketChannel.class)
				  .handler(new TcpFeignClientInitializeHandler());
	}
	
	
	@Override
	public void connect() throws Exception {
		//	初始化
		init();
		
		//	建立链接
		mainChannel = bootstrap.connect(host, port).sync().channel();
		mainChannel.attr(AttributeKey.valueOf(AttrbuteKeyNamespace.REMOTE_HOST)).set(host);
		mainChannel.attr(AttributeKey.valueOf(AttrbuteKeyNamespace.REMOTE_PORT)).set(port);
		mainChannel.attr(AttributeKey.valueOf(AttrbuteKeyNamespace.CLIENT_USERNAME)).set(username);
		mainChannel.attr(AttributeKey.valueOf(AttrbuteKeyNamespace.CLIENT_PASSWORD)).set(password);
		mainChannel.attr(AttributeKey.valueOf(AttrbuteKeyNamespace.CLIENT_SUFFIX)).set(keySuffix);
		mainChannel.attr(AttributeKey.valueOf(AttrbuteKeyNamespace.LOGIN_SUCCESS)).set(new Runnable() {
			public void run() {
				if (loginCallback != null) {loginCallback.accept(DefaultTcpFeignClient.this);}
			}
		});
		if (respCallback != null) {
			mainChannel.attr(AttributeKey.valueOf(AttrbuteKeyNamespace.BIZ_RESP_CALLBACK)).set(respCallback);
		}
		
		try {
			sendLogin(username, password);
		} catch (ActiveException e) {
			log.warn("[DefaultTcpFeignClient connect] 建立链接失败 " 
										+ "host:" + host + " port:" + port + 
										" username:" + username + " password:" + password + 
										" id:" + mainChannel.id());
			throw new Exception(e);
		}

		log.error("[DefaultTcpFeignClient connect] 建立链接成功，发送登录消息 " +
										" host:" + host + " port:" + port + 
										" username:" + username + " password:" + password + 
										" id:" + mainChannel.id());
	}

	
	@Override
	public void close() {
		try {
			mainChannel.close().sync();
			workerGroup.shutdownGracefully().sync();
		} catch (InterruptedException e) {
			log.error("[DefaultTcpFeignClient close] " + e.getMessage(), e);
		}
	}

	
	@Override
	public void sendLogin(String username, String password) throws ActiveException {
		if (!mainChannel.isActive()) {
			throw new ActiveException("客户端连接状态异常");
		}
		
		//	发送登录消息
		mainChannel.eventLoop().execute(() -> mainChannel.writeAndFlush(LoginByteMessage.build(username, password)));
	}


	@Override
	public void send(AbstractByteMessage msg) throws ActiveException, LoginException {
		if (!mainChannel.isActive()) {
			throw new ActiveException("客户端连接状态异常");
		}
		//	等待5s的登录状态
		int times = 5;
		int isLogin = -2;
		while (times-- > 0) {
			isLogin = isLogin();
			if (isLogin == 1) {break;}
			else if (isLogin < 0) {break;}
			
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				log.error("[DefaultTcpFeignClient send] 线程中断 name:" + Thread.currentThread().getName());
			}
		}
		if (isLogin() != 1) {
			throw new LoginException("客户端登录异常 loginStatus:" + (isLogin == 0 ? "登录超时" : "登录失败"));
		}
		
		mainChannel.eventLoop().execute(() -> mainChannel.writeAndFlush(msg));
	}
	
	
	@Override
	public boolean isActive() {
		if (mainChannel == null) {return false;}
		if (!mainChannel.isActive()) {return false;}
		return true;
	}
	/**
	 * 检测客户端是否登录成功
	 * <p>	登录成功的回执收到以后，才算登录成功
	 * @return	-2:链接还没建立 | -1:登录失败 | 0:登录中 | 1:登录成功
	 */
	public int isLogin() {
		if (!isActive()) {return -2;}

		if (mainChannel.attr(AttributeKey.valueOf(AttrbuteKeyNamespace.LOGIN)).get() == null) {return 0;}
		Boolean login = (Boolean)mainChannel.attr(AttributeKey.valueOf(AttrbuteKeyNamespace.LOGIN)).get();
		return login.equals(true) ? 1 : -1;
	}


	@Override
	public int port() {
		return port;
	}


	@Override
	public String host() {
		return host;
	}
	
	
	@Override
	public String suffix() {
		return keySuffix;
	}


	@Override
	public String username() {
		return username;
	}


	@Override
	public String password() {
		return password;
	}


	@Override
	public long connectTimestamp() {
		Object t = mainChannel.attr(AttributeKey.valueOf(AttrbuteKeyNamespace.CONNECT_TIMESTAMP)).get();
		if (t == null) { return -1; }
		else {return (Long)t;}
	}


	@Override
	public void bindLoginCallback(Consumer<ITcpFeignClient> loginCallback) {
		this.loginCallback = loginCallback;
	}


	@Override
	public void bindBizRespCallback(Consumer<BizRespByteMessage> respCallback) {
		this.respCallback = respCallback;
	}


	@Override
	public String toString() {
		return String.format("[%s:%s suffix:%s %s:%s]", host, port, keySuffix, username, password);
	}

	
}
